
#include <assert.h>
#include <fcntl.h>
#include <iostream>
#include <sstream>

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>


#include "Utils.h"
#include "AppLogger.h"
#include "HttpHelper.h"

using namespace std;

static const char *optString = "+u:c:f:t:p:h?";

static AppLogger * logger = AppLogger::getInstance("nwProg");

enum ClientMode {
	GET, PUT
};

static struct ProgConfig {
	string http_url; //the http_url of the server

	enum ClientMode clientMode; //the mode (get/put) of the client program

	string localFileName; // name of the local file which will be uploaded if client mode is "PUT"

	string contentType; // the content-type that should be associated with this file

	string proxyHost;	// proxy host

	int proxyPort;		// proxy port

} progConfig; // the program configuration

// print the program configuration
static void printProgConfig(ProgConfig config);

//parsing the command line options
static int parseCommandLine(int argc, char *argv[]);

/**
 * The SIG_INT handler
 */
void sigIntHandler(int sigNo);

static string UserAgentName = "nwprogClient 1.0";

// main function
int main(int argc, char ** argv) {

	// sets up the SIGINT handler for the main process
	Utils::setupSigHandler(SIGINT, sigIntHandler);

	// parse the command line options
	parseCommandLine(argc, argv);

	printProgConfig(progConfig);

	HttpRequest httpRequest;

	httpRequest.webProxyHost = progConfig.proxyHost;
	httpRequest.webProxyPort = progConfig.proxyPort;
	httpRequest.requestUri = progConfig.http_url;

	httpRequest.userAgent = UserAgentName;

	// set the "Host" header
	ostringstream oss;
	oss << httpRequest.getHostNameInUrl() << ":" << httpRequest.getHostPortInUrl();
	httpRequest.hostHdrValue =  oss.str();

	string iamHeaderStr = "Iam: shaohong";
	httpRequest.additionalHeaders.push_back(iamHeaderStr);

	// add some additional headers
	httpRequest.additionalHeaders.push_back("Accept: */*");
	httpRequest.additionalHeaders.push_back("Connection: close");

	if (progConfig.clientMode == GET) {
		httpRequest.method = "GET";

		HttpResponse httpResponse;
		HttpHelper::retrieveURIContent(httpRequest, httpResponse);
	}
	if (progConfig.clientMode == PUT) {

		//add Content-Type header
		string contentTypeStr = "Content-Type: " + progConfig.contentType;
		httpRequest.additionalHeaders.push_back(contentTypeStr);

		int32_t fileSize = Utils::getFileSize(progConfig.localFileName.c_str());

		if (fileSize < 0) {
			fprintf(stderr, "failed to open file %s\n",
					progConfig.localFileName.c_str());
			exit(-1);
		}

		//add Content-Length header
		char fileSizeStr[20];
		sprintf(fileSizeStr, "%d", fileSize);
		string contentLengthStr = "Content-Length: " + string(fileSizeStr);
		httpRequest.additionalHeaders.push_back(contentLengthStr);

		httpRequest.method = "PUT";
		HttpResponse httpResponse;

		// open the local file and
		int fd; /* File descriptors */
		/* Use File descriptors */
		fd = open(progConfig.localFileName.c_str(), O_RDONLY);
		if (fd == -1) {
			perror("Opening local file failed");
			logger->error("Failed to open %s",
					progConfig.localFileName.c_str());
			exit(1);
		}

		HttpHelper::putURIContent(httpRequest, httpResponse, fd);

		close(fd);
	}

	return 0;
}

/*
 * Print the usage information to the user
 */
void printUsage(void) {
	cout
			<< "======\n"
			<< "usage:\n"
			<< "======\n"
			<< "	nwprogclient [options] "
			<< endl
			<< endl
			<< "Options:\n"
			<< "    -c[get/put] Specifies the client mode.\n"
			<< "    \"get\"  will retrieve the HTTP page from server. \"put\" will upload a local file to the http server"
			<< endl
			<< endl
			<< "    -u http_url Specify the HTTP URL"
			<< endl
			<< endl
			<< "    -p proxyHost:port Specify the web proxy to be used by the client"
			<< endl
			<< endl
			<< "    -f path_of_local_file. This option is used in combination with \"-c put\" option to"
					" upload a local file to the HTTP server"
			<< endl
			<< endl
			<< "    -t content-type. This option specifies the content-type of the file to be uploaded. \n"
					"e.g. image/jpeg, text/plain, etc, following rfc2387" << endl << endl;

}

int parseCommandLine(int argc, char *argv[]) {

	// check the argument list

	// if argument list is empty, print the usage.
	if (1 == argc) {
		printUsage();

		exit(EXIT_FAILURE);
	}

	/* Process the arguments with getopt(), then
	 * populate streamerConfig.
	 */
	int opt = getopt(argc, argv, optString);
	while (opt != -1) {

		switch (opt) {
		case 'u': // the HTTP URL
			if (NULL != optarg) {
				progConfig.http_url = string(optarg);
			} else {
				printUsage();
				exit(EXIT_FAILURE);
			}
			break;

		case 'c': // the client mode
			if (NULL != optarg) {
				string clientModeOptStr = string(optarg);
				if (0 == clientModeOptStr.compare("get")) {
					progConfig.clientMode = GET;
				}

				if (0 == clientModeOptStr.compare("put")) {
					progConfig.clientMode = PUT;
				}
			} else {
				printUsage();
				exit(EXIT_FAILURE);
			}
			break;

		case 'f': // path to local file
			if (NULL != optarg) {
				progConfig.localFileName = string(optarg);
			} else {
				printUsage();
				exit(EXIT_FAILURE);
			}
			break;

		case 't': // content type
			if (NULL != optarg) {
				progConfig.contentType = string(optarg);
			} else {
				printUsage();
				exit(EXIT_FAILURE);
			}
			break;

		case 'p': // web proxy
			if (NULL != optarg) {
				string proxyInfo = string(optarg);
				size_t colonPos = proxyInfo.find(':');
				if (colonPos != string::npos) {
					progConfig.proxyHost = proxyInfo.substr(0,colonPos);
					progConfig.proxyPort = atoi(proxyInfo.substr(colonPos+1).c_str());
					break;
				}
				cerr << "invalid proxy info" << endl;
			}

			printUsage();
			exit(EXIT_FAILURE);

		case 'h':
		case '?':
			printUsage();
			exit(EXIT_FAILURE);
			break;

		default:
			printUsage();

			exit(EXIT_FAILURE);
		}

		opt = getopt(argc, argv, optString);
	}

	if (progConfig.clientMode == PUT) {
		// check local file name is specified
		if (progConfig.localFileName.empty()) {
			fprintf(stderr, "please specify finame with -f option\n");

			printUsage();

			exit(EXIT_FAILURE);
		}

		// check content-type is specified
		if (progConfig.contentType.empty()) {
			fprintf(stderr, "please specify Content-Type with -t option\n");

			printUsage();

			exit(EXIT_FAILURE);
		}

	}
	return 0;
}

void printProgConfig(ProgConfig config) {
	cerr << "Program configurations: " << endl;
	cerr << "	http_url = " << config.http_url << endl;
	cerr << "	clientMode = " << config.clientMode << endl;

	if (!config.localFileName.empty()) {
		cerr << "	localFileName = " << config.localFileName << endl;
	}

	if (!config.contentType.empty()) {
		cerr << "	Content-Type = " << config.contentType << endl;
	}

	if (!config.proxyHost.empty()) {
		cerr << "  proxy = " << config.proxyHost << ":" << config.proxyPort << endl;
	}

}


void sigIntHandler(int sigNo) {

	fprintf(stderr, "received SIGINT!!!\n");

	HttpHelper::cancelProcessing();

	exit(EXIT_FAILURE);

}
